#[macro_use]
extern crate clap;
extern crate nix;
extern crate pnet;
extern crate ordered_float;
extern crate thrift;
extern crate try_from;

use clap::{Arg, App};

use pnet::datalink;
use std::io::{Error, prelude::*, BufReader, Write};
use nix::sys::socket::{self, sockopt::ReuseAddr, sockopt::BindToDevice};
use std::net::{IpAddr, Ipv4Addr};
use std::os::unix::io::AsRawFd;
use std::net::UdpSocket;
use std::ffi::OsString;
use std::thread;
use std::time;

mod msg;

use thrift::protocol::{TCompactInputProtocol, TCompactOutputProtocol, 
  TBinaryInputProtocol, TBinaryOutputProtocol, TMultiplexedOutputProtocol };
use thrift::transport::{
    ReadHalf, TFramedReadTransport, TFramedWriteTransport, TIoChannel, TTcpChannel, WriteHalf,
    TBufferedReadTransport, TBufferedWriteTransport
};


use msg::{MessageServiceSyncClient, TMessageServiceSyncClient};

type ClientInputProtocol = TBinaryInputProtocol<TBufferedReadTransport<ReadHalf<TTcpChannel>>>;
type ClientOutputProtocol = TBinaryOutputProtocol<TBufferedWriteTransport<WriteHalf<TTcpChannel>>>;

pub static mut PORT:u16 = 9091;

fn new_client(
  host: &str,
  port: u16,
) -> thrift::Result<MessageServiceSyncClient<ClientInputProtocol, ClientOutputProtocol>> {
  let mut channel = TTcpChannel::new();

  // open the underlying TCP stream
  //println!("connecting to device server on {}:{}", host, port);
  channel.open(&format!("{}:{}", host, port))?;

  // clone the TCP channel into two halves, one which
  // we'll use for reading, the other for writing
  let (i_chan, o_chan) = channel.split()?;

  // wrap the raw sockets (slow) with a buffered transport of some kind
  let i_tran = TBufferedReadTransport::new(i_chan);
  let o_tran = TBufferedWriteTransport::new(o_chan);

  // now create the protocol implementations
  let i_prot = TBinaryInputProtocol::new(i_tran, false);
  let o_prot = TBinaryOutputProtocol::new(o_tran, false);
  //let protocol = TBinaryOutputProtocol::new(o_tran, true);
  //let o_prot = TMultiplexedOutputProtocol::new("misc", protocol);

  // we're done!
  Ok(MessageServiceSyncClient::new(i_prot, o_prot))
}

fn gen_json(port:u16) -> json::JsonValue {
  let mut jdata = json::JsonValue::new_object();
  let mut arr = json::JsonValue::new_array();

  for iface in datalink::interfaces().iter()
        .filter(|e| !e.is_loopback() && e.ips.len() > 0) {
    let mut ifinfo = json::JsonValue::new_object();
    
    ifinfo["name"] = json::JsonValue::String(String::from(&iface.name));
    ifinfo["mac"] = json::JsonValue::String(iface.mac.unwrap().to_string());
    ifinfo["addr"] = json::JsonValue::String(iface.ips.first().unwrap().ip().to_string());

    arr.push(ifinfo).expect("json push error");
  }
  jdata["network"] = arr;

  match new_client("127.0.0.1", port) {
    Ok(mut client) => {
      
      let mut smsg = msg::Message::default();
      smsg.tag = Some("sysInfo".to_string());
      
      let rmsg = client.message(smsg);
      let ddata = json::parse(&rmsg.unwrap().msg.unwrap()).unwrap();
      jdata["info"] = ddata;
    },
    Err(e) => println!("Error: {}", e),
  }

  //println!("recv msg:{:?}", ddata);
  

  jdata
}

fn handle_client(listener: UdpSocket, port: u16) -> Result<(), Error>{
    let mut buf = [0; 4096];
    let fd = listener.as_raw_fd();
    listener.set_broadcast(true).expect("set_broadcast call failed");
    socket::setsockopt(fd, ReuseAddr, &true).expect("set ReuseAddr failed");
    socket::setsockopt(fd, BindToDevice, &OsString::from("eth0")).expect("set BindToDevice failed");

    loop {
      let (amt, mut src) = listener.recv_from(&mut buf)?;
      src.set_ip(IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)));

      let r = &mut buf[..amt];

      //println!("amt:{:?}  src:{:?} r:{:?}", amt, src, r);
      let patten: &'static str = "zynqBT";
      let _bb = 'b' as u8;
      
      if &r[..6] == patten.as_bytes() {
        match r[6] {
          _bb => {
            let jval = gen_json(port);
            
            listener.send_to(jval.dump().as_bytes(), &src)?;
          },
          _ =>  println!("something else"),
        };
      }
      

      thread::sleep(time::Duration::from_micros(1 as u64));
    }
}

fn main() {
	let matches = App::new("Here")
      .version(crate_version!())
      .author("bamboo")
      .about("At here")
      .arg("-tp, --tport=[port] 'thrift port'")
      .arg("-p, --port=[port] 'udp bind port'")
      .get_matches();


  // get any passed-in args or the defaults
  let host =  String::from(matches.value_of("host").unwrap_or("127.0.0.1"));
  let tport = matches.value_of_t("tport").unwrap_or(9091);
  let port: u16 = matches.value_of_t("port").unwrap_or(20099);
  
  println!("thrift port:{:?}, udp port:{:?}", tport, port);

	thread::spawn(move || {
    let listener = UdpSocket::bind(&format!("0.0.0.0:{}", port)).expect("couldn't bind to address");
    handle_client(listener, tport)
  });

  loop {
    thread::sleep(time::Duration::from_secs(10 as u64));
  }
}
